import type { ComputePositionConfig, VirtualElement } from '@floating-ui/dom'
import { type Editor, Extension } from '@tiptap/core'
import type { Node } from '@tiptap/pm/model'

import { DragHandlePlugin } from './drag-handle-plugin'

export const defaultComputePositionConfig: ComputePositionConfig = {
  placement: 'left-start',
  strategy: 'absolute',
}

export interface DragHandleOptions {
  /**
   * 渲染一个使用 floating-ui/dom 包定位的元素
   */
  render(): HTMLElement
  /**
   * 使用 floating-ui/dom 包计算拖拽手柄位置的配置
   */
  computePositionConfig?: ComputePositionConfig
  /**
   * 返回拖拽手柄的虚拟元素的函数
   * 当菜单需要相对于特定 DOM 元素定位时，这很有用
   */
  getReferencedVirtualElement?: () => VirtualElement | null
  /**
   * 锁定拖拽手柄的位置和可见性
   */
  locked?: boolean
  /**
   * 当鼠标悬停在节点上时返回节点或 null
   */
  onNodeChange?: (options: { node: Node | null; editor: Editor }) => void
  /**
   * 拖拽开始时调用的回调函数
   */
  onElementDragStart?: (e: DragEvent) => void
  /**
   * 拖拽结束时调用的回调函数
   */
  onElementDragEnd?: (e: DragEvent) => void
}

declare module '@tiptap/core' {
  interface Commands<ReturnType> {
    dragHandle: {
      /**
       * 锁定拖拽手柄的位置和可见性
       */
      lockDragHandle: () => ReturnType
      /**
       * 解锁拖拽手柄
       */
      unlockDragHandle: () => ReturnType
      /**
       * 切换拖拽手柄的锁定状态
       */
      toggleDragHandle: () => ReturnType
    }
  }
}

export const DragHandle = Extension.create<DragHandleOptions>({
  name: 'dragHandle',

  addOptions() {
    return {
      render() {
        const element = document.createElement('div')

        element.classList.add('drag-handle')

        return element
      },
      computePositionConfig: {},
      locked: false,
      onNodeChange: () => {
        return null
      },
      onElementDragStart: undefined,
      onElementDragEnd: undefined,
    }
  },

  addCommands() {
    return {
      lockDragHandle:
        () =>
        ({ editor }) => {
          this.options.locked = true
          return editor.commands.setMeta('lockDragHandle', this.options.locked)
        },
      unlockDragHandle:
        () =>
        ({ editor }) => {
          this.options.locked = false
          return editor.commands.setMeta('lockDragHandle', this.options.locked)
        },
      toggleDragHandle:
        () =>
        ({ editor }) => {
          this.options.locked = !this.options.locked
          return editor.commands.setMeta('lockDragHandle', this.options.locked)
        },
    }
  },

  addProseMirrorPlugins() {
    const element = this.options.render()

    return [
      DragHandlePlugin({
        computePositionConfig: { ...defaultComputePositionConfig, ...this.options.computePositionConfig },
        getReferencedVirtualElement: this.options.getReferencedVirtualElement,
        element,
        editor: this.editor,
        onNodeChange: this.options.onNodeChange,
        onElementDragStart: this.options.onElementDragStart,
        onElementDragEnd: this.options.onElementDragEnd,
      }).plugin,
    ]
  },
})
